/**
 * 
 */
package group.anglers.om.core.persistence.impls;

import group.anglers.om.core.commons.ScriptReader;
import group.anglers.om.core.exceptions.BatchException;
import group.anglers.om.core.exceptions.ModelException;
import group.anglers.om.core.exceptions.OmException;
import group.anglers.om.core.exceptions.UnExpectedException;
import group.anglers.om.core.persistence.Persistence;
import group.anglers.om.core.persistence.Query;
import group.anglers.om.core.persistence.serialization.ModelSerializationArchived;
import group.anglers.om.core.persistence.serialization.impl.ModelSerialization;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * @author an.pham
 *
 */
public abstract class PersistenceImpl implements Persistence {
	private ModelSerializationArchived modelSerializationPool;
	
	public PersistenceImpl() {
	}
	
	public PersistenceImpl(ModelSerializationArchived modelSerializationPool) {
		this.modelSerializationPool = modelSerializationPool;
	}

	@Override
	public void insert(Object model) throws OmException {
		Class<? extends Object> modelClass = model.getClass();
		
		ModelSerialization modelSerialization = modelSerializationPool.get(modelClass);
		
		if(modelSerialization.getTableName() == null){
			throw new ModelException("\"" + modelClass.getName() + "\" there is no mapping table in this model "
					+ "therefore the Insert operation can not be executed !!!");
		}
		
		Query<? extends Object> query = createQuery(CRUDQueryBuilder.buildInsertQuery(modelSerialization)
												, model.getClass());
		/* get primary column */
		SqlUtils.setParameter(modelSerialization.getPrimaryKey(), model, modelClass, query);
		/* get normal columns */
		SqlUtils.setParameter(modelSerialization.getColumns(), model, modelClass, query);
		
		query.executeUpdate();
	}
	
	@Override
	public void update(Object model) throws OmException {
		Class<? extends Object> modelClass = model.getClass();
		
		ModelSerialization modelSerialization = modelSerializationPool.get(modelClass);
		
		if(modelSerialization.getTableName() == null){
			throw new ModelException("\"" + modelClass.getName() + "\" there is no mapping table in this model "
					+ "therefore the Update operation can not be executed !!!");
		}
		
		Query<?> query = createQuery(CRUDQueryBuilder.buildUpdateQuery(modelSerialization), modelClass);
		/* get normal columns */
		SqlUtils.setParameter(modelSerialization.getColumns(), model, modelClass, query);
		/* get primary column */
		SqlUtils.setParameter(modelSerialization.getPrimaryKey(), model, modelClass, query);
		
		query.executeUpdate();
	}

	@Override
	public void delete(Object model) throws OmException {
		Class<? extends Object> modelClass = model.getClass();
		
		ModelSerialization modelSerialization = modelSerializationPool.get(modelClass);
		
		if(modelSerialization.getTableName() == null){
			throw new ModelException("\"" + modelClass.getName() + "\" there is no mapping table in this model "
					+ "therefore the Delete operation can not be executed !!!");
		}
		
		Query<?> query = createQuery(CRUDQueryBuilder.buildDeleteQuery(modelSerialization), modelClass);
		/* get primary column */
		SqlUtils.setParameter(modelSerialization.getPrimaryKey(), model, modelClass, query);
		
		query.executeUpdate();
	}

	@Override
	public <T> T findByPK(Object primaryKey, Class<T> modelClass) throws OmException {
		ModelSerialization modelSerialization = modelSerializationPool.get(modelClass);
		
		Query<? extends T> query = createQuery(CRUDQueryBuilder.buildFindByPKQuery(modelSerialization), modelClass);
		/* get primary column */
		try{
			SqlUtils.setParameter(primaryKey, modelSerialization.getPrimaryKey(), query);
		}catch(ModelException e){
			throw new ModelException("\"" + modelClass.getName()
					+ "\" " + e.getMessage());
		}
		
		return query.getSingleResult();
	}
	
	@Override
	public <T> List<T> findAll(Class<T> modelClass) throws OmException {
		ModelSerialization modelSerialization = modelSerializationPool.get(modelClass);
		
		Query<T> query = createQuery(CRUDQueryBuilder.buildFindAllPKQuery(modelSerialization), modelClass);
		
		return query.getResultList();
	}
	
	@Override
	public void executeBatch(InputStream inputStream) throws OmException {
		try {
			BufferedReader bufferedReader = null;
			Statement statement = null;
			ScriptReader scriptReader = null;
			
			try{
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
				
				statement = getConnection().createStatement();
				
				scriptReader = new ScriptReader(bufferedReader);
				
				String sql = null;
				
				while(true){
					sql = scriptReader.readStatement();
					
					if(sql == null || sql.trim().isEmpty()){
						break;
					}
					
					statement.addBatch(sql);
				}
				
				statement.executeBatch();
			} finally {
				if(bufferedReader != null){
					bufferedReader.close();
				}
				
				if(statement != null){
					statement.close();
				}
				
				if(scriptReader != null){
					scriptReader.close();
				}
			}
		} catch (UnsupportedEncodingException e) {
			throw new BatchException(e);
		} catch (SQLException e) {
			throw new BatchException(e);
		} catch (Exception e) {
			throw new UnExpectedException(e);
		}
	}

	@Override
	public void flush() throws OmException {
		try {
			getConnection().close();
		} catch (SQLException e) {
			throw new UnExpectedException(e);
		}
	}
	
	@Override
	public void startTransaction() throws OmException {
		try {
			getConnection().setAutoCommit(false);
		} catch (SQLException e) {
			throw new UnExpectedException(e);
		}
		
	}
	
	@Override
	public void commitTransaction() throws OmException {
		try {
			getConnection().commit();
		} catch (SQLException e) {
			throw new UnExpectedException(e);
		}
	}
	
	@Override
	public void rollbackTransaction() throws OmException {
		try {
			getConnection().rollback();
		} catch (SQLException e) {
			throw new UnExpectedException(e);
		}
	}

	@Override
	public <T> Query<T> createQuery(String sql, Class<T> modelClass) throws OmException {
		return new QueryImpl<T>(getConnection(), sql, modelSerializationPool.get(modelClass));
	}
	
	
	protected abstract Connection getConnection() throws OmException;

}
